package com.meng.thrift.client.thrift.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashMap;


/**
 *
 * 对GetThriftServerObj注解的解析类
 *
 * 实现Bean实例化 自定义注解的属性注入(autowired) 原理就是autowired
 *
 *  AutowiredAnnotationBeanPostProcessor也是实现了这个接口
 *  （InstantiationAwareBeanPostProcessor 适配器类：InstantiationAwareBeanPostProcessorAdapter）的postProcessPropertyValues方法。
 *  这个方法里，扫描了实例化的bean带有注解的字段,并将对象注入到Bean的这个属性中。
 *
 * @author MENG
 * @version 2017/11/16
 * @see
 */
@Component //注解注入spring 扫描地方     springmvc也要手动注入英文springmvc 扫描的地方也要用到该类
public class GetThriftServerObjAnalysis extends InstantiationAwareBeanPostProcessorAdapter
{
    public static final Logger logger = LoggerFactory.getLogger(GetThriftServerObjAnalysis.class);

    public GetThriftServerObjAnalysis()
    {
        //被扫描了两次初始化了两次  thriftServerObjMap 第一次的数据就没了 要使用静态变量 只会被初始化一次

        System.out.println("GetThriftServerObjInject-----------------------------------------------扫描");
    }

    /**
     * Thrift server object Cache
     *
     * static var are not empty
     */
    private static final HashMap<String,Object> thriftServerObjMap = new HashMap<>();


    @Override
    public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
    {
//        System.out.println(beanName+"-------- 属性设置");

        //根据Bean得到当前的 bean 中的属性
        Field[] fields = bean.getClass().getDeclaredFields();

        for(Field field :fields)
        {
            //判断bean中的属性是否有 GetThriftServerObj 注解
            //有执行自己的注入逻辑
            if (field.isAnnotationPresent(GetThriftServerObj.class))
            {
                GetThriftServerObj obj = field.getAnnotation(GetThriftServerObj.class);

                // 做缓存 用同一个对象 不频繁的去 thriftServer 获取对象（主要是服务端无法进行动态更新 所以获取多次都是同一个对象没有意义）
                Object value = null;

                if(thriftServerObjMap.get(field.getName()) != null)
                {
                    value = thriftServerObjMap.get(field.getName());
                }
                else
                {
                    String classPath = field.getType().getName();

                    String clientName = classPath.substring(classPath.lastIndexOf("$")+1);

                    value = ThriftClientFactory.createThriftObject(field.getType(),ThriftIpPortEnum.MENGLOCAL_IP_PORT_8191,clientName);

                    if(value == null)
                    {
                        logger.error("根据clientName创建ThriftServer对象错误:{}", clientName);
                    }

                    thriftServerObjMap.put(field.getType().getName(),value); //下次直接那缓存的value
                }

                try
                {
                    ReflectionUtils.makeAccessible(field);//让私有属性 可以注入

                    field.set(bean, value);
                }
                catch (IllegalAccessException e)
                {
                    logger.error("创建ThriftServer对象错误:{}", field.getType(), e);
                }
            }
        }

        return pvs;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {

//        System.out.println(beanName+"-------- 实例化之前设置");

        return null;
    }

    /**
     *
     * postProcessAfterInstantiation 和 postProcessPropertyValues都可以对Bean进行设置
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {

        //FIXME  了解postProcessAfterInstantiation方法和postProcessPropertyValues的方法区别在哪
        //FIXME 有问题 ：为属性赋值要放在postProcessPropertyValues 方法中 不然会报错
//        System.out.println(beanName+"-------- 实例化之后设置");

        return true;
    }


}
